♻️ refactor(web-sdk_angular): Align SSR with isomorphic runtime#357
Merged
Conversation
…ization-web/runtime
Lotfi Anwar L Arif (Lotfi-Arif)
pushed a commit
that referenced
this pull request
Jul 8, 2026
* feat(web-sdk_angular): Align SSR with isomorphic runtime seam * refactor(web-sdk,react-web-sdk): Move WebOptimizationRuntime to optimization-web/runtime * refactor(web-sdk_angular): Resolve merge tags at render time * refactor(web-sdk_angular): Split tracking-log event switch into methods * chore(web-sdk): Raise runtime subpath bundle-size budgets
Lotfi Anwar L Arif (Lotfi-Arif)
pushed a commit
that referenced
this pull request
Jul 8, 2026
* feat(web-sdk_angular): Align SSR with isomorphic runtime seam * refactor(web-sdk,react-web-sdk): Move WebOptimizationRuntime to optimization-web/runtime * refactor(web-sdk_angular): Resolve merge tags at render time * refactor(web-sdk_angular): Split tracking-log event switch into methods * chore(web-sdk): Raise runtime subpath bundle-size budgets
Tim Beyer (TimBeyer)
added a commit
that referenced
this pull request
Jul 8, 2026
Bring in main's breaking changes so the docs branch has a clean point-in-time base to fix against: - fix(nextjs): Next.js 15 export bug (#361) — package root export removed; /app-router and /pages-router no longer re-export React Web (now /client) - feat(cda): managed entry fetching (#360) — SDK can fetch via an app-owned Contentful client (contentful config, fetchOptimizedEntry, entryId props), changing the "you fetch, the SDK resolves" boundary - refactor(web-sdk_angular): SSR isomorphic runtime (#357) The 5 doc files edited on both sides (guides/AGENTS.md + the 4 web-family guides) were resolved take-ours — our teach-first rewrites are kept intact; main's mechanical edits to the old versions are superseded. All of main's source, new guides, concepts, and READMEs merge in cleanly. Docs are now correct in structure but sit on changed source: a follow-up team pass patches the guides for #360 (managed fetch, boundary reframe, the fetchOptimizedEntry helper name collision) + #361 (export-boundary troubleshooting), and updates the SDK knowledge base to match. Not yet done in this commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lotfi Anwar L Arif (Lotfi-Arif)
added a commit
that referenced
this pull request
Jul 8, 2026
…ntry resol… (#359) * ✨ feat(optimization): Implement empty variant handling in entry resolution - Introduced support for an **empty variant** (id: "") in the entry personalization and variant resolution process. - Updated the resolver to return an `isEmptyVariant` flag in `ResolvedData`, allowing renderers to suppress content while still emitting component view impressions. - Enhanced documentation to clarify the behavior of empty variants and their distinction from baseline selections and resolution errors. - Added tests to ensure correct handling of empty variants, including logging behavior and tracking context preservation. This update improves the personalization experience by allowing content authors to intentionally show no content for specific audiences while maintaining tracking capabilities. * 🔧 chore(core-sdk): Update bundle size for index.mjs to reflect new optimization metrics * ✨ feat(documentation, api-schemas, core-sdk): Clarify empty variant handling in personalization and variant resolution - Updated documentation to specify the distinction between empty variants and data errors, detailing how empty variants are represented in the Personalization UI and their detection mechanism. - Enhanced the Zod schema to reflect the dual states of the `hidden` flag in Contentful-sourced data and emphasized the use of `id === ""` for detecting empty variants. - Revised the resolver comments to clarify the handling of empty variants and their implications for rendering and tracking. These changes improve the understanding of empty variant behavior, ensuring accurate implementation and measurement in personalization scenarios. * 🚀 ci(slack): Fixing issue with the Slack Notification script * ♻️ refactor(web-sdk_angular): Align SSR with isomorphic runtime (#357) * feat(web-sdk_angular): Align SSR with isomorphic runtime seam * refactor(web-sdk,react-web-sdk): Move WebOptimizationRuntime to optimization-web/runtime * refactor(web-sdk_angular): Resolve merge tags at render time * refactor(web-sdk_angular): Split tracking-log event switch into methods * chore(web-sdk): Raise runtime subpath bundle-size budgets * 🔥 feat(cda): Add managed entry fetching (#360) Add Core `contentful` configuration, cached `fetchContentfulEntry()`, and `fetchOptimizedEntry()` APIs for stateful and request-bound stateless SDK flows. Introduce shared entry-source lifecycle support, optimized-entry metadata, and managed-entry keying so Web Components, React Web, React Native, and Next.js helpers can resolve entries from `entryId`/`entryQuery` while preserving the manual `baselineEntry` path. Add React Web SSR and hybrid entry handoff support with `serverOptimizedEntries`, prefetch helpers, snapshot-runtime fetch guards, and hydration-safe managed entry lookup. Add Next.js App Router and Pages Router managed-entry support, including server-rendered `<OptimizedEntry entryId>`, Pages Router `prefetchOptimizedEntries`, server-fetched entry handoffs, and client-safe provider config. Refresh docs, guides, reference implementations, and tests for managed entry fetching across Core, Web Components, React Web, React Native, and Next.js. Adjust the Web Components UMD gzip budget for the managed-entry surface while keeping event constants readable. [[NT-3558](https://contentful.atlassian.net/browse/NT-3558)] * 🐞 fix(nextjs): Fixing Next.js 15 export bug (#361) Next.js 15 rejects wildcard re-exports across client boundaries, which breaks live updates and preview panel usage when SDK client entries are compiled into a client graph. Replace Next.js SDK client-boundary wildcard barrels with explicit named exports and keep router entrypoints scoped to router factories and route trackers. Remove the ambiguous package-root export so consumers must choose the correct server, client, router, schema, ESR, request-handler, or tracking subpath. Add a shared build-tools guard that fails when a `"use client"` file contains `export *`, and run it from the React Web and Next.js SDK builds. Update tests and documentation for the new import boundaries. BREAKING CHANGE: `@contentful/optimization-nextjs` no longer exports from the package root. Use explicit subpaths such as `/app-router`, `/pages-router`, `/pages-router/server`, `/client`, `/server`, `/api-schemas`, `/esr`, `/request-handler`, or `/tracking-attributes`. BREAKING CHANGE: `/app-router` and `/pages-router` no longer re-export generic React Web hooks, providers, or components. Import those from `/client`. [[NT-3614](https://contentful.atlassian.net/browse/NT-3614)] * ♻️ refactor(web-sdk_angular): Align SSR with isomorphic runtime (#357) * feat(web-sdk_angular): Align SSR with isomorphic runtime seam * refactor(web-sdk,react-web-sdk): Move WebOptimizationRuntime to optimization-web/runtime * refactor(web-sdk_angular): Resolve merge tags at render time * refactor(web-sdk_angular): Split tracking-log event switch into methods * chore(web-sdk): Raise runtime subpath bundle-size budgets * 🔥 feat(cda): Add managed entry fetching (#360) Add Core `contentful` configuration, cached `fetchContentfulEntry()`, and `fetchOptimizedEntry()` APIs for stateful and request-bound stateless SDK flows. Introduce shared entry-source lifecycle support, optimized-entry metadata, and managed-entry keying so Web Components, React Web, React Native, and Next.js helpers can resolve entries from `entryId`/`entryQuery` while preserving the manual `baselineEntry` path. Add React Web SSR and hybrid entry handoff support with `serverOptimizedEntries`, prefetch helpers, snapshot-runtime fetch guards, and hydration-safe managed entry lookup. Add Next.js App Router and Pages Router managed-entry support, including server-rendered `<OptimizedEntry entryId>`, Pages Router `prefetchOptimizedEntries`, server-fetched entry handoffs, and client-safe provider config. Refresh docs, guides, reference implementations, and tests for managed entry fetching across Core, Web Components, React Web, React Native, and Next.js. Adjust the Web Components UMD gzip budget for the managed-entry surface while keeping event constants readable. [[NT-3558](https://contentful.atlassian.net/browse/NT-3558)] * 🐞 fix(nextjs): Fixing Next.js 15 export bug (#361) Next.js 15 rejects wildcard re-exports across client boundaries, which breaks live updates and preview panel usage when SDK client entries are compiled into a client graph. Replace Next.js SDK client-boundary wildcard barrels with explicit named exports and keep router entrypoints scoped to router factories and route trackers. Remove the ambiguous package-root export so consumers must choose the correct server, client, router, schema, ESR, request-handler, or tracking subpath. Add a shared build-tools guard that fails when a `"use client"` file contains `export *`, and run it from the React Web and Next.js SDK builds. Update tests and documentation for the new import boundaries. BREAKING CHANGE: `@contentful/optimization-nextjs` no longer exports from the package root. Use explicit subpaths such as `/app-router`, `/pages-router`, `/pages-router/server`, `/client`, `/server`, `/api-schemas`, `/esr`, `/request-handler`, or `/tracking-attributes`. BREAKING CHANGE: `/app-router` and `/pages-router` no longer re-export generic React Web hooks, providers, or components. Import those from `/client`. [[NT-3614](https://contentful.atlassian.net/browse/NT-3614)] * 🔧 chore(core-sdk): Update gzip budget for index.mjs to 19100 * 🔧 chore(web-sdk): Update gzip budgets for index.mjs and contentful-optimization-web components * ✨ feat(optimized-entry): Refactor OptimizedEntry component to improve error handling and loading states - Introduced `OptimizedEntryBodyProps` interface for better type safety. - Created `renderOptimizedEntryBody` function to encapsulate rendering logic for different states (error, loading, resolved). - Updated `OptimizedEntry` component to utilize the new rendering function, enhancing readability and maintainability. - Improved handling of duplicate baseline ancestors and loading fallbacks. * 🔧 chore(react-web-sdk): Update gzip budgets for index.cjs and index.mjs to 4800 and 4100 respectively --------- Co-authored-by: Charles Hudson <charles.hudson@contentful.com> Co-authored-by: Felipe Mamud <felipe.mamud@contentful.com> Co-authored-by: Charles Hudson <265039+phobetron@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Refactor the Angular reference implementation to adopt the
OptimizationRuntime/OptimizationSnapshotseam introduced in #354/#355, so Angular renders personalized content during SSR from snapshot state and hydrates into the live browser SDK using the same primitives React Web uses. Along the way, remove local machinery that now duplicates behavior available in@contentful/optimization-core/@contentful/optimization-web, and consolidate the merge-tag rendering pattern with Next.js.To make one of those primitives Angular-consumable, promote
WebOptimizationRuntime/createWebSnapshotRuntimefrom a React-internal file into@contentful/optimization-web/runtimeso every framework layer (React Web, Angular, and future adapters) reads from one canonical seam.Motivation
platform: 'server' | 'browser'runtime discriminator, its own pre-resolved-entries TransferState payload, its own rich-text merge-tag walker, and its ownifBrowser/ifLiveescape hatches — all things the isomorphic runtime primitives now cover uniformly.runtime().*unconditionally, merge tags resolve at render time via agetMergeTagValueclosure, and the framework layer onlyhandles the constructor branch. Angular should match.
Changes
1. Angular reference implementation — adopt the isomorphic seam
services/optimization.ts— singleSignal<WebOptimizationRuntime>seeded viacreateWebSnapshotRuntime(snapshot); promoted to the liveContentfulOptimizationafterhydrateOptimizationDataresolves. Preflight simplified to emit oneOptimizationSnapshot(matches the shape Next.js uses forserverOptimizationState).ifBrowser/ifLiveescape hatches deleted — every SDK-facing call site now goes throughruntime().*unconditionally.services/entry.ts— collapsed the server/browser branches: one path callsruntime.resolveOptimizedEntryandruntime.getMergeTagValue. Deleted the TransferState lift, deletedmergeTagResolvedfrom the entry-resolution layer, droppedisPlatformBrowserfromsetupManualTrackingsinceruntime().tracking.*NOOPs on the snapshot runtime.components/entry-card/index.ts— rich-text walker takes agetMergeTagValueclosure captured fromruntime()+profile()and substitutesINLINES.EMBEDDED_ENTRYmerge-tag targets in place, matching Next.js'screateRichTextRenderOptionspattern. One shared walk produces both the HTML and the merge-tag badge signal.components/tracking-log/index.ts— the inlineswitch (raw?.type)became a 4-line dispatcher; each event branch is now a privatehandle*method typed withExtract<OptimizationEventStreamEvent, { type: '…' }>. Sequence counters moved from closure locals to private class fields.services/live-updates.ts—previewPanelAttached/previewPanelOpenread fromruntime().states.*instead of the previousifBrowserwrapping.components/control-panel/index.ts,pages/page-two/index.ts— callruntime().consent,runtime().identify,runtime().reset,runtime().page,runtime().trackViewunconditionally.src/app/transfer-state-keys.ts— the twoStateKeys now live next to their sole owners (SERVER_BASELINES_KEYincontentful-client.ts,SERVER_OPTIMIZATION_KEYinternal tooptimization.ts).src/app/services/merge-tags.ts— the ~52-LOCresolveEntryMergeTagspre-walker; substitution now happens at render time.2. Package change — publish
WebOptimizationRuntimefromoptimization-web/runtimeWebOptimizationRuntimeandcreateWebSnapshotRuntimemoved frompackages/web/frameworks/react-web-sdk/src/runtime/webRuntime.ts(internal) topackages/web/web-sdk/src/runtime.ts(public, alongside the existing pass-through of@contentful/optimization-core/runtime). This composes the universalOptimizationRuntimewith the browser-only web SDK surface (tracking,trackCurrentPage) once, so every framework consumer imports the same isomorphic runtime object.OptimizationContext/OptimizationProviderre-pointed at@contentful/optimization-web/runtime.webRuntime.tsfile deleted.Design notes
isPlatformBrowserchecks kept intentionally. Inservices/optimization.tsit gatesnew ContentfulOptimization()(the SDK readslocalStorageat construction time — this is the root of the runtime seam, not a caller of it). Inservices/consent.tsit gates a rawdocument.cookiewrite (unrelated to the SDK). Both guard things the runtime seam explicitly does not cover.runServerPreflight) is kept. Next.js's equivalent is baked intocreateNextjsAppRouterOptimization, which is Next.js-shaped. Angular'sprovideServerOptimizationInitializer()is the Angular flavor of the same per-framework concern; it emits oneOptimizationSnapshotand stops there.SERVER_RESOLVED_ENTRIES_KEYintentionally dropped. Server-resolved entries are recomputable fromdata.selectedOptimizations+ baseline via the snapshot runtime's pure resolvers; shipping them wastes bytes and creates a second source of truth. Bit-identical output.typescript:S7059without introducing dead-codethison instance methods.Breaking changes
None for consumers of Angular reference impl or the SDK packages. The moved
WebOptimizationRuntimewas internal toreact-web-sdk; the new public location is additive.Test plan
pnpm implementation:run -- web-sdk_angular typecheck— cleanpnpm exec eslint implementations/web-sdk_angular/src/— cleanpnpm exec prettier --check implementations/web-sdk_angular/src/— cleanpnpm --filter @contentful/optimization-web typecheck+test:unit— cleanpnpm --filter @contentful/optimization-react-web typecheck+test:unit— cleanpnpm build:pkgs— cleanpnpm setup:e2e:web-sdk_angular && pnpm test:e2e:web-sdk_angular— Playwright SSR/first-paint suite (please verify in CI)pnpm implementation:run -- web-sdk_angular dev, exercise home + page-two routes, consent toggle, identify/reset, live-updates toggle, preview panel